#include "ylcd.h"

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/mman.h>
#include <linux/fb.h>
#include <sys/ioctl.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>

typedef uint32_t mmap_return_type;  // 根据色深决定

struct{
    int __fd;
    struct fb_var_screeninfo __varinfo;
    mmap_return_type *_fbp; 
    uint32_t size;
}lcd;

// static mmap_return_type *_fbp=NULL; 
// struct fb_var_screeninfo __varinfo;
// static int __fd=-1;

static void reset_struct_lcd();
static void destory_struct_lcd();

static bool get_varinfo(int fd);

// 初始化
bool init_lcd()
{
    // 初始化lcd
    reset_struct_lcd();
    // 打开文件
    lcd.__fd = open("/dev/fb0", O_RDWR);
    if (lcd.__fd < 0 || !get_varinfo(lcd.__fd)) return false; 
    lcd.size = lcd.__varinfo.xres*lcd.__varinfo.yres;
    // 映射空间
    lcd._fbp = (mmap_return_type*)mmap(NULL, lcd.size*(lcd.__varinfo.bits_per_pixel/8),
                PROT_READ | PROT_WRITE, MAP_SHARED, lcd.__fd, 0);
    if (lcd._fbp == NULL) return false;

    atexit(destroy_lcd);

    // printf("%u\n", lcd.size);

    return true;
}

// 结束lcd
void destroy_lcd()
{
    if (lcd._fbp != NULL){
        // 解除映射
        munmap(lcd._fbp, lcd.size);
        close(lcd.__fd);
        lcd._fbp = NULL;
        lcd.__fd = -1;
    }
}

// 获取lcd显示屏的分辨率
bool get_xyRes(int32_t *x, int32_t *y)
{
    if (lcd._fbp == NULL) return false;
    *x = lcd.__varinfo.xres;
    *y = lcd.__varinfo.yres;
    return true;
}

// 清屏  将屏幕绘制为 color 的颜色
void clear_lcd(uint32_t color)
{
    if (lcd._fbp == NULL) return ;
    // mmap_return_type *p = lcd._fbp;
    for (uint32_t i=0; i<lcd.size; ++i){
        lcd._fbp[i] = color;
        // printf("%d\n", i);
    }
}

void clear_rect(uint32_t color, int32_t x, int32_t y, uint32_t width, uint32_t height)
{
    mmap_return_type *p = lcd._fbp;
    p = p + y*lcd.__varinfo.xres + x;
    for (int i=0; i<height; ++i){
        for (int j=0; j<width; ++j){
            p[j] = color;
        }
        p += lcd.__varinfo.xres;
    }
}

// 像素数据， 坐标、图像的宽、高  图像的色深  绘制图像
GraphRect draw_graph(void *pixeldata, int32_t x, int32_t y, uint32_t width,
                     uint32_t height, uint32_t bytePixel, bool isReverse)
{
    mmap_return_type *p = lcd._fbp;
    int32_t lcdwid = lcd.__varinfo.xres;
    uint8_t *data = (char*)pixeldata;
    // int coeff = 1;
    // 把 p 移动到 pos(x,y)
    p = p + x + y * lcd.__varinfo.xres;
    // printf("p %p\n", p);
    if (isReverse){
        p = p + lcd.__varinfo.xres * (height-1);    // 移动到最后一行
        // coeff = -1;
        lcdwid *= -1;
    }

    for (int i=0; i<height; ++i){
        for (int j=0; j<width;  ++j){
            memcpy(p+j, data+j*bytePixel, bytePixel);
            // printf("line %d\n", __LINE__);
        }
// printf("index %d\n", i);
        data += width*bytePixel;
        // printf("data %u\n", *data);
        p += lcdwid;
        // printf("p %u\n", *p);
       
    }

    return (GraphRect){x, y, width, height/*, bits_per_pixel*/};
}

// 绘制矩形
GraphRect draw_rect(int32_t x, int32_t y, uint32_t width, uint32_t height, uint32_t color)
{
    mmap_return_type *p = lcd._fbp;
    p = p + x + y*lcd.__varinfo.xres;

    mmap_return_type *up = p, *down = p + (height-1)*lcd.__varinfo.xres;
    mmap_return_type *left = p, *right = p+width-1;

    // 画水平线
    for (int i=0; i<width; ++i){
        up[i] = color;
        down[i] = color;
    }

    uint32_t lcd_wid = lcd.__varinfo.xres;
    for (int i=0; i<height; ++i){
        *(left+i*lcd_wid) = color;
        *(right+i*lcd_wid) = color;
    }

    return (GraphRect){x, y, width, height};
}

static void reset_struct_lcd()
{
    lcd.__fd = -1;
    lcd._fbp = NULL;
    lcd.size = 0;
}

static void destory_struct_lcd()
{
    munmap(lcd._fbp, lcd.size);
    close(lcd.__fd);
    reset_struct_lcd();
}

static bool get_varinfo(int fd)
{
    if (ioctl(fd, FBIOGET_VSCREENINFO, &lcd.__varinfo) != 0){
        return false;
    }
    return true;
}